﻿//TxtSource.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.History.Rts
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Interop;

	using StockSharp.BusinessEntities;

	using LumiSoft.Net.FTP.Client;

	class TxtSource : FileSource
	{
		private string[] _txtFiles;
		private DateTime _lastListDate;

		public TxtSource(ISecurityStorage securityStorage, SecurityIdGenerator securityIdGenerator)
			: base(securityStorage, securityIdGenerator)
		{
		}

		protected override IDictionary<Security, List<Trade>> OnGetTrades(FTP_Client client, DateTime date)
		{
			if (_lastListDate.Date != DateTime.Today)
			{
				// CSV
				// 2011/FT110204.zip
				_txtFiles = client
					.GetList(FtpDirectory)
					.Where(item => item.IsDir)
					.Select(item => item.Name)
					.SelectMany(year =>
						client
							.GetList(FtpDirectory + "/" + year)
							.Where(item => item.Name.ContainsIgnoreCase(".zip") && (ExchangeBoard == ExchangeBoard.Ux || item.Name.ContainsIgnoreCase("FT")))
							.Select(item => year + "/" + item.Name))
					.ToArray();

				_lastListDate = DateTime.Today;
			}

			var localPath = DownloadCsv(client, date);

			var trades = new Dictionary<Security, List<Trade>>();

			if (null != localPath)
			{
				ProcessCsv(trades, localPath, date);
				ProcessXls(trades, localPath);
			}

			return trades;
		}

		private string DownloadCsv(FTP_Client client, DateTime date)
		{
			var dateStr = "{0:00}{1:00}{2:00}".Put(date.Year - 2000, date.Month, date.Day);

			var remoteFile = _txtFiles.FirstOrDefault(name => name.ContainsIgnoreCase(dateStr));

			if (null == remoteFile)
				return null;

			var localPath = Path.Combine(DumpFolder, dateStr);
			var localFile = Path.Combine(DumpFolder, (ExchangeBoard == ExchangeBoard.Ux ? string.Empty : "FT") + dateStr + ".zip");

			if (!File.Exists(localFile))
			{
				client
					.DownloadFile(FtpDirectory, remoteFile, localFile)
					.Extract(localPath);
			}
			else if (!Directory.Exists(localPath))
			{
				Directory.CreateDirectory(localPath);
				localFile.Extract(localPath);
			}

			return localPath;
		}

		private void ProcessCsv(IDictionary<Security, List<Trade>> trades, string tempDir, DateTime date)
		{
			var csvFiles = Directory.GetFiles(tempDir, "*.csv");

			var emptyMinStepSizeSecurities = new List<Security>();

			foreach (var file in csvFiles)
			{
				var fileName = Path.GetFileNameWithoutExtension(file);
				if (fileName == null)
					throw new InvalidOperationException("Неправильное название у файла {0}.".Put(file));

				SecurityTypes secType;

				fileName = fileName.ToLowerInvariant();

				if (fileName.EndsWith("ft"))
					secType = SecurityTypes.Future;
				else if (fileName.EndsWith("ot"))
					secType = SecurityTypes.Option;
				else
					throw new InvalidOperationException("Неправильное название у файла {0}.".Put(fileName));

				//code;contract;price;amount;dat_time;trade_id;Nosystem
				//RI150000BT0;RTS-9.10M160810PA 150000;3775.00000;2;2010-07-29 19:00:29.620;201815146;0

				using (var rdr = new StreamReader(file))
				{
					const char sep = ';';

					//code;contract;price;amount;dat_time;trade_id;Nosystem
					var line = rdr.ReadLine();

					if (line == null)
						throw new InvalidOperationException("Файл {0} не содержит заголовка.".Put(file));

					var columns = line.Split(sep);
					var codeCol = columns.IndexOf("code");
					var nameCol = columns.IndexOf("contract");
					var priceCol = columns.IndexOf("price");
					var amountCol = columns.IndexOf("amount");
					var timeCol = columns.IndexOf("dat_time");
					var idCol = columns.IndexOf("trade_id");
					var nosysCol = columns.IndexOf("Nosystem");

					while ((line = rdr.ReadLine()) != null)
					{
						var items = line.Split(sep);

						var secCode = items[codeCol];
						var secName = items[nameCol];
						var price = items[priceCol].RemoveTrailingZeros().To<decimal>();
						var amount = items[amountCol].To<decimal>();
						var tradeTime = items[timeCol].ToDateTime("yyyy-MM-dd HH\\:mm\\:ss.fff");
						var isNoSystem = nosysCol >= 0 &&
							(
								items[nosysCol] != "0" &&
								!items[nosysCol].IsEmpty() // http://forum.rts.ru/viewtopic.asp?p=37863 (c 01.11.2006 по 14.02.2007 значение пустое)
							);

						if (IsSystemOnly && isNoSystem)
							continue;

						var tradeId = items[idCol].To<long>();

						// за 15.09.2008 (опционы) есть 5 строчек с пустыми кодами и именами инструментов
						if (secName == string.Empty && date == new DateTime(2008, 9, 15))
						{
							switch (tradeId)
							{
								case 1453121:
									secCode = "RI120000X8";
									secName = "RTS-12.08_121208PA 120000";
									break;
								case 1453187:
									secCode = "SV12.5L8";
									secName = "SILV-12.08_121208CA 12.5";
									break;
								case 1453190:
									secCode = "SV11X8";
									secName = "SILV-12.08_121208PA 11";
									break;
								case 1453191:
									secCode = "SV11L8";
									secName = "SILV-12.08_121208CA 11";
									break;
								case 1453308:
									secCode = "VB3500X8";
									secName = "VTBR-12.08_101208PA 3500";
									break;
								default:
									throw new InvalidOperationException("Неправильный номер сделки {0}.".Put(tradeId));
							}
						}

						var secId = SecurityIdGenerator.GenerateId(secCode, null, ExchangeBoard);
						var security = SecurityStorage.LoadBy("Id", secId);

						if (security == null)
						{
							security = new Security
							{
								Id = secId,
								Code = secCode,
								Name = secName,
								Class = string.Empty,
								ExtensionInfo = new Dictionary<object, object>(),
								ExchangeBoard = ExchangeBoard,
								Type = secType,
							};

							SecurityStorage.Save(security);
							emptyMinStepSizeSecurities.Add(security);
						}
						else if (security.MinStepSize == 0) // инструмент был создан через FinamHistorySource
						{
							security.Type = secType;
							emptyMinStepSizeSecurities.Add(security);
						}

						trades.SafeAdd(security).Add(new Trade
						{
							Id = tradeId,
							Security = security,
							Price = price,
							Time = tradeTime,
							Volume = amount,
							IsSystem = !isNoSystem,
						});
					}
				}
			}

			foreach (var pair in trades)
			{
				pair.Value.Sort(CompareTrades);
			}

			UpdateMinSteps(emptyMinStepSizeSecurities, trades);
		}

		private static int CompareTrades(Trade a, Trade b)
		{
			if (a.Id == b.Id)
				return 0;

			if (a.Time == b.Time)
				return a.Id > b.Id ? 1 : -1;

			return a.Time > b.Time ? 1 : -1;
		}

		private void ProcessXls(IDictionary<Security, List<Trade>> trades, string tempDir)
		{
			var emptyMinStepSizeSecurities = new List<Security>();

			foreach (var file in Directory.GetFiles(tempDir, "*.xls"))
			{
				using (var excel = new ExcelWorker(file, true))
				{
					var sheets = new[] { "futures_trades", "options_trades" };

					foreach (var sheet in sheets)
					{
						if (!excel.ContainsSheet(sheet))
							throw new InvalidOperationException("Листок {0} не был найден в файле {1}.".Put(sheet, file));

						excel.SwitchSheet(sheet);

						//code	contract	price	amount	dat_time
						var header = new List<string>();

						for (var col = 0; col < excel.GetColumnsCount(); col++)
							header.Add(excel.GetCell<string>(col, 0));

						var timeCol = header.IndexOf("dat_time");
						var codeCol = header.IndexOf("code");
						var nameCol = header.IndexOf("contract");
						var priceCol = header.IndexOf("price");
						var amountCol = header.IndexOf("amount");
						var idCol = header.IndexOf("trade_id");
						var rowCount = excel.GetRowsCount();

						// в 2004-ом колонки не было
						//if (idCol == -1)
						//	throw new InvalidOperationException("Колонка trade_id не найдена.");

						var secType = (sheet == "futures_trades") ? SecurityTypes.Future : SecurityTypes.Option;

						const long defaultTradeId = 111;

						for (var row = 1; row < rowCount; row++)
						{
							var tradeTime = excel.GetCell<DateTime>(timeCol, row);
							var secCode = excel.GetCell<string>(codeCol, row);
							var secName = excel.GetCell<string>(nameCol, row);
							var price = excel.GetCell<decimal>(priceCol, row);
							var amount = excel.GetCell<decimal>(amountCol, row);
							var tradeId = idCol == -1 ? defaultTradeId : excel.GetCell<long>(idCol, row);

							var secId = SecurityIdGenerator.GenerateId(secCode, null, ExchangeBoard);
							var security = SecurityStorage.LoadBy("Id", secId);

							if (security == null)
							{
								security = new Security
								{
									Id = secId,
									Code = secCode,
									Name = secName,
									Type = secType,
									Class = string.Empty,
									ExtensionInfo = new Dictionary<object, object>(),
									ExchangeBoard = ExchangeBoard,
								};

								SecurityStorage.Save(security);
								emptyMinStepSizeSecurities.Add(security);
							}
							else if (security.MinStepSize == 0) // инструмент был создан через FinamHistorySource
							{
								security.Type = secType;
								emptyMinStepSizeSecurities.Add(security);
							}

							trades.SafeAdd(security).Add(new Trade
							{
								Id = tradeId,
								Security = security,
								Price = price,
								Time = tradeTime,
								Volume = amount
							});
						}
					}
				}
			}

			UpdateMinSteps(emptyMinStepSizeSecurities, trades);
		}

		private void UpdateMinSteps(IEnumerable<Security> securities, IDictionary<Security, List<Trade>> trades)
		{
			if (securities == null)
				throw new ArgumentNullException("securities");

			foreach (var security in securities)
			{
				if (security.Type == SecurityTypes.Future && security.Name.ContainsIgnoreCase("RTS-"))
					security.MinStepSize = 5;
				if (security.Name.ContainsIgnoreCase("GOLD-"))
					security.MinStepSize = 0.1m;
				else if (security.Name.ContainsIgnoreCase("MIBR-"))
					security.MinStepSize = 0.01m;
				else if (security.Name.ContainsIgnoreCase("RTSc-"))
					security.MinStepSize = 0.05m;
				else if (security.Name.ContainsIgnoreCase("RTSo-"))
					security.MinStepSize = 0.05m;
				else if (security.Id == "URH7@RTS")
					security.MinStepSize = 0.01m;
				else
				{
					var securityTrades = trades[security].Where(t => t.IsSystem);

					if (securityTrades.IsEmpty())
					{
						// хоть что-то нужно сделать
						security.MinStepSize = 1;
					}
					else
					{
						var decimals = securityTrades.Max(t => t.Price.GetDecimalInfo().EffectiveScale);
						security.MinStepSize = 10m.Pow(-decimals);
					}
				}

				SecurityStorage.Save(security);
			}
		}
	}
}